﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.WebControls;

namespace FullFolderPathInLibraryBreadcrumb.FullFolderPathInLibraryBreadcrumbWebPart
{
    [ToolboxItemAttribute(false)]
    public class FullFolderPathInLibraryBreadcrumbWebPart : WebPart
    {
        protected override void CreateChildControls()
        {
        }

        /// <summary>
        /// Do all the work in prerender stage to exclude as many interdependencies as possible
        /// </summary>
        protected override void OnPreRender(EventArgs e)
        {
            try
            {
                base.OnPreRender(e);

                // Get the path to current folder - SharePoint places it in RootFolder query string parameter
                string path = Context.Request.QueryString["RootFolder"];

                // if there's no path there'snothing to do - we're not in library context
                if (String.IsNullOrEmpty(path))
                {
                    return;
                }

                // Let's get the current folder
                SPWeb web = SPContext.Current.Web;
                SPFolder currentFolder = web.GetFolder(path);

                // Let's find the breadcrumb control on the current page - it's a ListProperty control which "Property" property is set to "TitleBreadcrumb". It renders OOTB breadcrumb.
                Control c = Utils.FindRecursive(Page.Controls, ctrl => ctrl is ListProperty && ((ListProperty)ctrl).Property == "TitleBreadcrumb");

                // If there's no such control there's nothing we can do - it's not the OOTB library page
                if (c == null)
                    return;

                // Let's subsitute OOTB breadcrumb control with our imitation
                var parent = c.Parent;
                var index = parent.Controls.IndexOf(c);
                parent.Controls.RemoveAt(index);
                parent.Controls.AddAt(index, new LiteralControl { Text = GetNewBreadCrumbOutput(currentFolder) });
            }
            catch (Exception ex)
            {
                // It's an utility web part and we don't want to break page if an error occurs. So log it silently
                Utils.WriteLog(Utils.GetErrorInfo(ex));
            }
        }

        /// <summary>
        /// Create an HTML output for breadcrumb imitation
        /// </summary>
        /// <param name="folder">SPFolder to create navigation to</param>
        /// <returns>HTML output</returns>
        private string GetNewBreadCrumbOutput(SPFolder folder)
        {
            List<BreadcrumbNodeData> nodes = new List<BreadcrumbNodeData>();

            // Collect a path from current folder to it's root folder
            SPFolder nodeFolder = folder;
            while (nodeFolder != null)
            {
                // If we're in folder use the folder name as a title. If not use a luibrary title enstead.
                nodes.Add(new BreadcrumbNodeData { Title = string.IsNullOrEmpty(nodeFolder.ParentFolder.Url) ? nodeFolder.DocumentLibrary.Title : nodeFolder.Name, Url = nodeFolder.ServerRelativeUrl });
                nodeFolder = string.IsNullOrEmpty(nodeFolder.ParentFolder.Url) ? null : nodeFolder.ParentFolder;
            }

            // Reverse the collected path because the root folder must be on the left in bredcrumb
            nodes.Reverse();

            // Create an HTML output similar to original. An arrow image we've created from the original
            string htmlOutput = String.Empty;

            foreach (var node in nodes)
            {
                if (node != nodes.Last())
                    htmlOutput +=
                        String.Format(
                            @"<A href=""{0}"">{1}</A>&nbsp;<IMG style=""vertical-align:middle"" alt=: src=""/_layouts/images/FullFolderPathInLibraryBreadcrumb/breadcrumb_arrow.png""/>&nbsp;", node.Url, node.Title);
                else
                {
                    htmlOutput += node.Title;
                }
            }

            return htmlOutput;
        }
    }

    /// <summary>
    /// It's a "temporary" class to holds navigation node data
    /// </summary>
    public class BreadcrumbNodeData
    {
        /// <summary>
        /// Title for URL (it will be a folder name)
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Url to navigate on click (it will be a server relative URL of the folder)
        /// </summary>
        public string Url { get; set; }
    }

    public class Utils
    {
        public static string GetErrorInfo(Exception ex)
        {
            string result = "ex.Message=" + ex.Message;
            result += ex.InnerException == null ? "|ex.StackTrace=" + ex.StackTrace : String.Empty;

            if (ex.InnerException != null)
                result += "[INNER EXCEPTION: ]" + GetErrorInfo(ex.InnerException);

            return result;
        }

        public static void WriteLog(string text)
        {
            SPDiagnosticsService.Local.WriteTrace(0,
                                                  new SPDiagnosticsCategory("FullFolderPathInLibraryBreadcrumbWebPart", TraceSeverity.High,
                                                                            EventSeverity.Error),
                                                  TraceSeverity.High, text, null);
        }



        /// <summary>
        /// Finds a control based on provided criteria inside controls hierarchy
        /// </summary>
        /// <param name="controls">A Controls collection to start search</param>
        /// <param name="criteria">A criteria to return control</param>
        /// <returns>The founded control </returns>
        public static Control FindRecursive(ControlCollection controls, Func<Control, bool> criteria)
        {
            foreach (Control control in controls)
            {
                if (criteria(control))
                    return control;

                var innerControl = FindRecursive(control.Controls, criteria);
                if (innerControl != null)
                    return innerControl;
            }

            return null;
        }
    }
}
